<?php
/*
* Class teia\webdev\php\core\PaginationProvider
* Core class for providing division of content into discrete pages
*
* @author Bernardo Quibiana
* @date created: 20111129
*
* @date modified: 20111129
* @modified: Bernardo Quibiana
*/

// namespace declaration: Abstract Data Types
namespace teia\webdev\php\core;

require_once 'DisplayableItem.inc';
require_once 'IPaginationDataSource.inc';
require_once 'FunctionPool.inc';

class PaginationProvider extends DisplayableItem {
	
	private $data_source = null;
	private $current_page = 1;
	private $nav_link = null;
	
	private $source_page_offset = - 1;
	private $per_item_content = false;
	private $show_top_nav = true;
	private $show_bottom_nav = true;
	private $nav_adjacent_links = 5;
	
	private $nav_first_str = '&lt;&lt;';
	private $nav_prev_str = '&lt;';
	private $nav_next_str = '&gt;';
	private $nav_last_str = '&gt;&gt;';
	private $nav_jump_str = 'Page: ';
	
	private $nav_html_class = 'page_nav';
	private $nav_link_html_class = 'nav_link';
	private $content_html_class = 'page_content';
	private $item_html_class = 'page_item';
	private $page_html_class = 'page';
	
	/**
	 * Enter description here ...
	 * @param strign $id
	 * @param string $name
	 * @param array $var_array
	 * @param IPaginationDataSource $data_source
	 * @throws \InvalidArgumentException
	 */
	public function __construct($id, $name,  $var_array=NULL, $data_source) {
		parent::__construct($id, $name, $var_array);
		
		$this->current_page = 1;
		$this->nav_link = null;
		if ($data_source instanceof IPaginationDataSource) {
			$this->data_source = $data_source;
		} else {
			throw new \InvalidArgumentException('Invalid class for data source, expected: IPaginationDataSource', '999');
		}
	}
	
	/**
	 * Set the specified property
	 * @param unknown_type $property name of the property to change (properties ignored: data_source and current_page)
	 * @param unknown_type $value new value for the specified property
	 */
	public function set_property($property, $value) {
		if ($property != 'data_source' && $property != 'current_page') {
			$this->$property = $value;
		}
	}
	
	public function show_nav($show) {
		$this->show_top_nav = $show;
		$this->show_bottom_nav = $show;
	}
	
	public function set_per_item($per_item_content){
		$this->per_item_content = $per_item_content;
	}
	
	/**
	 * Enter description here ...
	 * @return number
	 */
	public function get_num_pages() {
		if ($this->per_item_content) {
			$num_items = $this->data_source->get_num_items();
			$page_size = $this->data_source->get_page_size();
			
			return ceil($num_items / $page_size);
		} else {
			return $this->data_source->get_num_pages();
		}
	}
	
	/**
	 * Return the number of the first page
	 * @return number first page
	 */
	public function get_first_page_num() {
		return abs($this->source_page_offset);
	}
	
	/**
	 * Return the number of the last page
	 * @return number last page
	 */
	public function get_last_page_num() {
		return ($this->get_num_pages() + $this->get_first_page_num() - 1);
	}
	
	public function display($context) {
		// attempt to determine page number
		if (isset($_REQUEST['page']) && is_numeric($_REQUEST['page'])) {
			$page_num = $_REQUEST['page'];
			//echo "page number defined in request: $page_num";
		} else {
			$page_num = $this->current_page;
			//echo "page number not defined in request: $page_num";
		}
		// display the page
		$this->display_page($page_num);
	}
	
	/**
	 * Display the specified page
	 * @param number $page_num number of the page to display
	 */
	public function display_page($page_num) {
		echo $this->generate_page_num($page_num);
	}
	
	/**
	 * Display the previous page
	 */
	public function display_prev_page() {
		$this->display_page($this->current_page - 1);
	}
	
	/**
	 * Display the next page
	 */
	public function display_next_page() {
		$this->display_page($this->current_page + 1);
	}
	
	/**
	 * Generate a displayable page
	 * @param number $page_num number of the page to display
	 * @return string the HTML code to display the targe page
	 */
	public function generate_page_num($page_num) {
		$first_page = $this->get_first_page_num();
		$last_page = $this->get_last_page_num();
	
		if ($last_page >= $first_page) {
			// clamp requested page number
			$this->current_page = FunctionPool::clamp($page_num, $first_page, $last_page);
			// generate navigation
			$page_nav = $this->generate_page_nav($this->current_page, $first_page, $last_page);
			// generate content
			$page_content = $this->generate_page_content($this->current_page);
		} else {
			$page_nav = '';
			$page_content = '';
		}
		// code to display
		$content = '<div class="' . $this->page_html_class . '">' . "\n";
		if ($this->show_top_nav) $content .= $page_nav;
		$content .= $page_content . "<br />\n";
		if ($this->show_bottom_nav) $content .= $page_nav;
		$content .= '</div><!-- End of page -->' . "\n";
		
		return $content;
	}
	
	/**
	 * Generate the displayable content for the specified page number
	 * @param number $page_num number of the current page to display
	 * @return string the HMTL content to display
	 */
	public function generate_page_content($page_num) {
		$page_size = $this->data_source->get_page_size();
		$source_page = $page_num + $this->source_page_offset;	// the index to use in the data source
		$page_content = '<div class="' . $this->content_html_class . '">' . "\n";
		
		if ($this->per_item_content) {
			// fetch page content on per item basis
			$it_offset = $source_page * $page_size;
			$it_limit = $it_offset + $page_size;
			for ($i = $it_offset; $i < $it_limit; $i++) {
				$it_content = $this->data_source->get_item_content($i, $source_page);
				if ($it_content) {
					$page_content .= '<div class="' . $this->item_html_class . '">' . "\n" . $it_content . '\n</div>' . "\n";
				}
			}
		} else {
			// fetch page content on per page basis
			$page_content .= $this->data_source->get_page_content($source_page) . "\n";
		}
		$page_content .= "</div><!-- End of page content -->\n";
		
		return $page_content;
	}	
	
	/**
	 * Generate the displayble page navigation
	 * @param number $current_page number of the current page
	 * @param number $first_page number of the first page
	 * @param number $last_page number of the last page
	 * @return string the HTML navigation code to display
	 */
	public function generate_page_nav($current_page, $first_page, $last_page) {
	
		$page_nav = '<div class="' . $this->nav_html_class . '">'. "\n";
		$page_nav .= $this->generate_backward_nav($current_page, $first_page);
		$page_nav .= $this->generate_main_nav($current_page, $first_page, $last_page);
		$page_nav .= $this->generate_forward_nav($current_page, $last_page);
		$page_nav .= '</div>' . "\n";
		
		return $page_nav;
	}
		
	/**
	* Generate the page numbers with navigation links
	* @param number $current_page number of the current page
	* @param number $first_page number of the first page
	* @param number $last_page number of the last page
	* @return string the HTML navigation code to display
	*/
	public function generate_main_nav($current_page, $first_page, $last_page) {
		$content = '';
		$num_links = $current_page - $first_page;
		if ($num_links > $this->nav_adjacent_links) {
			$content .= '<span class="disabled">...</span>' . "\n";
			$start = $current_page - $this->nav_adjacent_links;
		} else {
			$start = $first_page;
		}
		
		$content .= $this->generate_numebered_nav($start, $current_page);
		//$content .= '<span class="current">' . $current_page . '</span>' . "\n";
		$content .= "<span class=\"$this->nav_link_html_class\"><span class=\"current\">$this->current_page</span></span>\n";
			
		$num_links = $last_page - $current_page; 
		if ($num_links > $this->nav_adjacent_links) {
			$limit .= $current_page + $this->nav_adjacent_links + 1;
			$content .= $this->generate_numebered_nav($current_page + 1, $limit);
			//$content .= '<span class="disabled">...</span>' . "\n";
			$content .= "<span class=\"$this->nav_link_html_class\"><span class=\"disabled\">...</span></span>\n";
		} else {
			$limit = $last_page + 1;
			$content .= $this->generate_numebered_nav($current_page + 1, $limit);
		}
		
		return $content;
	}

	/**
	 * Generates page numbers with navigation links
	 * @param number $start number of the start page (incluvise)
	 * @param number $limit number of the last page (excluvise)
	 * @return string displayable string with navigation links
	 */
	public function generate_numebered_nav($start, $limit) {
		$content = '';
		for ($i = $start; $i < $limit; $i++) {
			$link =  $this->generate_nav_link($i);
			$content .= "<span class=\"$this->nav_link_html_class\"><a href=\"$link\">$i</a></span>\n";
		}
		return $content;
	}
	
	/**
	 * Generate the backward (first and previous) navigation links
	 * @param number $current_page number of the current page
	 * @param number $first_page number of the first page
	 * @return string the HTML navigation code to display
	 */
	public function generate_backward_nav($current_page, $first_page) {
		$prev = $current_page - 1;
		$content = '';
		if ($current_page == $first_page) {
			// show goto first link
			$content .= "<span class=\"$this->nav_link_html_class\"><span class=\"disabled\">$this->nav_first_str</span></span>\n";	
			// show goto previous link
			$content .= "<span class=\"$this->nav_link_html_class\"><span class=\"disabled\">$this->nav_prev_str</span></span>\n";
		} else {
			// show goto first link
			$first_link = $this->generate_nav_link($first_page);
			$content .= "<span class=\"$this->nav_link_html_class\"><a href=\"$first_link\">$this->nav_first_str</a></span>\n";
			// show goto previous link
			$prev_link = $this->generate_nav_link($prev);
			$content .= "<span class=\"$this->nav_link_html_class\"><a href=\"$prev_link\">$this->nav_prev_str</a></span>\n";
		}
		return $content;
	}
	
	/**
	 * Generate the forward (next and last) navigation links
	 * @param number $current_page number of the current page
	 * @param number $last_page number of the last page
	 * @return string the HTML navigation code to display
	 */
	public function generate_forward_nav($current_page, $last_page) {
		$next = $current_page + 1;
		$content = '';
		if ($current_page == $last_page) {
			// show goto next link
			$content .= "<span class=\"$this->nav_link_html_class\"><span class=\"disabled\">$this->nav_next_str</span></span>\n";
			// show goto last link
			$content .= "<span class=\"$this->nav_link_html_class\"><span class=\"disabled\">$this->nav_last_str</span></span>\n";
		} else {
			// show goto next link
			$next_link = $this->generate_nav_link($next);
			$content .= "<span class=\"$this->nav_link_html_class\"><a href=\"$next_link\">$this->nav_next_str</a></span>\n";
			// show goto last link
			$last_link = $this->generate_nav_link($last_page);
			$content .= "<span class=\"$this->nav_link_html_class\"><a href=\"$last_link\">$this->nav_last_str</a></span>\n";
		}
		return $content;
	}
	
	/**
	 * Generate a page selection navigation
	 * @param number $current_page number of the current page
	 * @param number $first_page number of the first page
	 * @param number $last_page number of the last page
	 * @return string the HTML navigation code to display
	 */
	public function generate_jump_nav($current_page, $first_page, $last_page) {
		$content = '';
		
		$link = $this->generate_nav_link('');
		$action = 'window.location="' . $link . '"+this.options[this.selectedIndex].value;';
		$content .= '<label class="page_nav" for="_select_page_">' . $this->nav_jump_str . '.</label>' . "\n";
		$content .= '<select id="_select_page_" class="page_nav" onchange="' . $action . '">' . "\n";
		// left of current page
		for ($i = $first_page; $i < $current_page; $i++) {
			$content .= '<option value="' . $i . '">' . $i . '</option>' . "\n";
		}
		// current page
		$content .= '<option value="' . $current_page . '" selected="selected">' . $current_page . '</option>' . "\n";
		// right of current page
		for ($i = $current_page + 1; $i <= $last_page; $i++) {
			$content .= '<option value="' . $i . '">' . $i . '</option>' . "\n";
		}
		$content .= '</select>' . "\n";
		
		return $content;
	}
	
	/**
	 * Generate the link to a target page number based on the URI of the current request
	 * @param number $page_num the target page number
	 * @return string HTML link to target page number
	 */
	public function generate_nav_link($page_num) {
		if (!$this->nav_link) {
			// generates base navigation link based on request URI
			$pattern = '#(\?|&)page\=(\d+)#i';	
			
			if (preg_match($pattern, $_SERVER['REQUEST_URI'])) {
				// the request contains page number already, remove it
				//echo "positive match<br />\n";
				$request = preg_replace($pattern, '', $_SERVER['REQUEST_URI']);
				//echo "Request: $request<br />";
			} else {
				// the request does not contain page number
				//echo "no match<br />\n";
				$request = $_SERVER['REQUEST_URI'];
			}
			$this->nav_link =  $request;
		} 
		
		if (preg_match('#\?\w+\=\w+#i', $this->nav_link)) {
			//echo "with other variables: <br />\n";
			$page_link = $this->nav_link . "&page=$page_num";
		} else {
			//echo "with no variables<br />\n";
			$page_link = $this->nav_link . "?page=$page_num";
		}
		
		
		
		return $page_link;
	}
}

?>